{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 钢琴\n",
    "\n",
    "运行这个笔记本，看看会发生什么。\n",
    "\n",
    "一旦游戏画布出现，点击它，使其获取到焦点。\n",
    "\n",
    "* 键盘图像改编自 [jack-keyboard](http://jack-keyboard.sourceforge.net/)。\n",
    "* 采样的木琴(Xylophone)音由[Versilian Community Sample Library](https://vis.versilstudios.com/vcsl.html)提供。\n",
    "* 音频可视化是由图形专家和研究人员[Alban Fichet](https://afichet.github.io/)设计的，他善意地将其包含在[CC BY 4.0 license](https://creativecommons.org/licenses/by/4.0/)下。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import sys\n",
    "import os"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sys.path.insert(0, os.path.abspath('./..'))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import jupylet.color\n",
    "\n",
    "from jupylet.app import App\n",
    "from jupylet.state import State\n",
    "from jupylet.label import Label\n",
    "from jupylet.sprite import Sprite\n",
    "from jupylet.shadertoy import Shadertoy, get_shadertoy_audio"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from jupylet.audio.bundle import *"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "app = App(width=512, height=420, quality=100)#, log_level=logging.INFO)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 默认示波器着色器\n",
    "\n",
    "下面单元格中的代码是一个简单的shadertoy着色器，用于显示音频示波器。 [Shadertoy shaders](http://shadertoy.com/) 是一种通过直接编程GPU来创建图形效果的简单方法:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "st0 = Shadertoy(\"\"\"\n",
    "\n",
    "    void mainImage( out vec4 fragColor, in vec2 fragCoord )\n",
    "    {\n",
    "        // Normalized pixel coordinates (from 0 to 1)\n",
    "        // 标准化像素坐标（从0到1）\n",
    "        vec2 uv = fragCoord / iResolution.xy;\n",
    "\n",
    "        // Time varying pixel color / 时变像素颜色\n",
    "        vec3 col = 0.2 + 0.2 * cos(iTime + uv.xyx + vec3(0, 2, 4));\n",
    "\n",
    "        float amp = texture(iChannel0, vec2(uv.x, 1.)).r; \n",
    "\n",
    "        vec3 sig = vec3(0.00033 / max(pow(amp - uv.y, 2.), 1e-6));\n",
    "\n",
    "        sig *= vec3(.5, .5, 4.) / 2.;\n",
    "\n",
    "        col += sig;\n",
    "\n",
    "        // Output to screen / 输出到屏幕\n",
    "        fragColor = vec4(col,1.0);\n",
    "    }\n",
    "    \n",
    "\"\"\", 512, 256, 0, 420, 0, 'left', 'top')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Alban Fichet的音频可视化着色器\n",
    "\n",
    "\n",
    "下面单元格中的代码是一个漂亮的shadertoy着色器，由图形专家和研究员[Alban Fichet](https://afichet.github.io/)编写，他善意地允许将其包含在[CC BY 4.0 license](https://creativecommons.org/licenses/by/4.0/)下:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "ba1 = Shadertoy(\"\"\"\n",
    "\n",
    "    vec3 hue2rgb(in float h) {\n",
    "        vec3 k = mod(vec3(5., 3., 1.) + vec3(h*360./60.), vec3(6.));\n",
    "        return vec3(1.) - clamp(min(k, vec3(4.) - k), vec3(0.), vec3(1.));\n",
    "    }\n",
    "\n",
    "    void mainImage( out vec4 fragColor, in vec2 fragCoord )\n",
    "    {\n",
    "        vec2 uv = fragCoord/iResolution.xy;\n",
    "\n",
    "        float aspect = iResolution.x / iResolution.y;\n",
    "        float blurr = 0.3;\n",
    "        float sharpen = 1.7;\n",
    "\n",
    "        vec2 maxWindow = vec2(3., 3./aspect);\n",
    "        uv = mix(-maxWindow, maxWindow, uv);\n",
    "\n",
    "        float r = dot(uv, uv);\n",
    "        float theta = atan(uv.y, uv.x) + 3.14;\n",
    "\n",
    "        float t = abs(2.*theta / (2.*3.14) - 1.);\n",
    "\n",
    "        float signal = 2.0*texture(iChannel0,vec2(t,1.)).x;\n",
    "        float ampl = 2.0*texture(iChannel0,vec2(0.8,.25)).x;\n",
    "\n",
    "        float v = 1. - pow(smoothstep(0., blurr, abs(r - signal)), 0.02);\n",
    "        float hue = pow(fract(abs(sin(theta/2.) * ampl)), sharpen);\n",
    "\n",
    "        fragColor = vec4(v * hue2rgb(fract(hue + iTime/10.)), 1.0);\n",
    "    }\n",
    "    \n",
    "\"\"\")\n",
    "\n",
    "bb1 = Shadertoy(\"\"\"\n",
    "\n",
    "    void mainImage( out vec4 fragColor, in vec2 fragCoord )\n",
    "    {\n",
    "        vec2 uv = fragCoord/iResolution.xy;\n",
    "\n",
    "        vec2 center = vec2(.5 + 0.15*sin(iTime));\n",
    "        float zoom = 1.02;\n",
    "\n",
    "        vec4 prevParams = texture(iChannel0, (uv - center)/zoom + center);\n",
    "        vec4 bB = texture(iChannel1, uv);\n",
    "\n",
    "        fragColor = clamp(mix(prevParams, 4 * bB, 0.1), 0., 1.) ;\n",
    "    }\n",
    "    \n",
    "\"\"\")\n",
    "\n",
    "bb1.set_channel(0, bb1)\n",
    "bb1.set_channel(1, ba1)\n",
    "\n",
    "st1 = Shadertoy(\"\"\"\n",
    "\n",
    "    void mainImage( out vec4 fragColor, in vec2 fragCoord )\n",
    "    {\n",
    "        vec3 bA = texelFetch(iChannel0, ivec2(fragCoord), 0).rgb;\n",
    "        vec3 bB = texelFetch(iChannel1, ivec2(fragCoord), 0).rgb;\n",
    "\n",
    "        vec3 col_rgb = bB;\n",
    "\n",
    "        col_rgb = col_rgb*exp2(3.5);\n",
    "\n",
    "        fragColor = vec4(pow(col_rgb, vec3(1./2.2)), 1.);\n",
    "    } \n",
    "\n",
    "\"\"\", 512, 256, 0, 420, 0, 'left', 'top')\n",
    "\n",
    "st1.set_channel(0, ba1)\n",
    "st1.set_channel(1, bb1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "keyboard_layout = Sprite('images/keyboard.png', x=256, y=82, scale=0.5)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "label0 = Label('amp: %.2f' % get_master_volume(), anchor_x='right', x=app.width - 10, y=174)\n",
    "label1 = Label('使用 ↑ ↓ 控制音量(use ↑ ↓ to control volume)', x=10, y=194)\n",
    "label2 = Label('空格键切换着色器(tap SPACE to change shader)', x=10, y=174)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "state = State(\n",
    "    \n",
    "    up = False,\n",
    "    down = False,\n",
    "    shader = 0,\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "keys = app.window.keys\n",
    "\n",
    "keyboard = {\n",
    "\n",
    "    keys.Z: note.C,\n",
    "    keys.S: note.Cs,\n",
    "    keys.X: note.D,\n",
    "    keys.D: note.Ds,\n",
    "    keys.C: note.E,\n",
    "    keys.V: note.F,\n",
    "    keys.G: note.Fs,\n",
    "    keys.B: note.G,\n",
    "    keys.H: note.Gs,\n",
    "    keys.N: note.A,\n",
    "    keys.J: note.As,\n",
    "    keys.M: note.B,\n",
    "\n",
    "    keys.Q: note.C5,\n",
    "    50: note.Cs5,\n",
    "    keys.W: note.D5,\n",
    "    51: note.Ds5,\n",
    "    keys.E: note.E5,\n",
    "    keys.R: note.F5,\n",
    "    53: note.Fs5,\n",
    "    keys.T: note.G5,\n",
    "    54: note.Gs5,\n",
    "    keys.Y: note.A5,\n",
    "    55: note.As5,\n",
    "    keys.U: note.B5,\n",
    "\n",
    "    keys.I: note.C6,\n",
    "    57: note.Cs6,\n",
    "    keys.O: note.D6,\n",
    "    48: note.Ds6,\n",
    "    keys.P: note.E6,\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "_keyd = {}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "@app.event\n",
    "def key_event(key, action, modifiers):\n",
    "            \n",
    "    keys = app.window.keys\n",
    "    value = action == keys.ACTION_PRESS\n",
    "\n",
    "    if key == keys.UP:\n",
    "        state.up = value\n",
    "\n",
    "    if key == keys.DOWN:\n",
    "        state.down = value\n",
    "\n",
    "    if key == keys.SPACE and action == keys.ACTION_PRESS:\n",
    "        state.shader = 1 - state.shader\n",
    "\n",
    "    if action == keys.ACTION_PRESS and key in keyboard:\n",
    "        assert key not in _keyd\n",
    "        _keyd[key] = tb303.play_poly(note=keyboard[key])\n",
    "        \n",
    "    if action == keys.ACTION_RELEASE and key in keyboard:\n",
    "        _keyd.pop(key).play_release()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "@app.run_me_every(1/24)\n",
    "def modify_volume(ct, dt):\n",
    "    \n",
    "    s = 2 ** dt\n",
    "    amp = get_master_volume()\n",
    "    \n",
    "    if state.up:\n",
    "        amp *= s\n",
    "        set_master_volume(amp)\n",
    "        label0.text = 'amp: %.2f' % amp\n",
    "\n",
    "    if state.down:\n",
    "        amp /= s\n",
    "        set_master_volume(amp)\n",
    "        label0.text = 'amp: %.2f' % amp"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Uncomment the following line to reduce latency to minimum. If this change introduces\n",
    "# sound artefacts, either set latency back to 'high' or switch your computer power\n",
    "# management to performance mode.\n",
    "# 取消对以下行的注释，以将延迟降至最低。\n",
    "# 如果此更改引入了声音干扰，请将延迟设置回  'high'，\n",
    "# 或将计算机电源管理切换到性能模式。\n",
    "#\n",
    "jupylet.audio.sound.set_latency('lowest')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#\n",
    "# Uncomment to set the tb303 wave function to square wave.\n",
    "# 取消注释，将tb303波函数设置为方波。\n",
    "#\n",
    "#tb303.osc0.shape = 'pulse'\n",
    "#"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#\n",
    "# Uncomment the following line to set up a beautiful reverb effect that \n",
    "# adds a sense of space and a touch of realism to the synthesized instrument. \n",
    "# Try it with a pair of good headphones. \n",
    "# 取消对以下行的注释，以设置一个美丽的混响效果，\n",
    "# 为合成乐器添加一种空间感和一点真实感。\n",
    "# 用一副好耳机试试。\n",
    "#\n",
    "set_effects(ConvolutionReverb('./sounds/impulses/MaesHowe.flac'))\n",
    "#"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#\n",
    "# The following line turns MIDI on. If you have an electric piano, hook it up \n",
    "# to your computer and it should just work.\n",
    "# 下一行打开MIDI。如果你有一架电钢琴，把它连接到你的电脑上，它就可以正常工作了。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "app.set_midi_sound(tb303)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "!pip install python-rtmidi"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "xylo = Sample('sounds/VCSL/Xylophone/Xylophone - Medium Mallets.sfz', amp=8)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#\n",
    "# Uncomment to set MIDI sound to a sampled Xylophone.\n",
    "# 取消注释以将MIDI声音设置为木琴音。\n",
    "#\n",
    "#app.set_midi_sound(xylo)\n",
    "#"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "@app.sonic_live_loop2(times=32)\n",
    "async def loop0():\n",
    "            \n",
    "    use(tb303, duration=2, amp=0.33)\n",
    "\n",
    "    play(C2)\n",
    "    await sleep(3)\n",
    "\n",
    "    play(E2)\n",
    "    await sleep(3)\n",
    "\n",
    "    play(C2)\n",
    "    await sleep(6)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "@app.sonic_live_loop\n",
    "async def loop1():\n",
    "    \n",
    "    use(xylo)\n",
    "        \n",
    "    play(C5)\n",
    "    await sleep(1)\n",
    "\n",
    "    play(E5)\n",
    "    await sleep(1)\n",
    "\n",
    "    play(G5)\n",
    "    await sleep(1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#\n",
    "# Uncomment and run to stop the two live loops.\n",
    "# 取消注释并运行以停止两个实时循环。\n",
    "#\n",
    "#app.stop(loop0)\n",
    "#app.stop(loop1)\n",
    "#"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#\n",
    "# Uncomment to see logging messages in case of trouble.\n",
    "# 取消注释以查看日志消息，以防出现问题。\n",
    "#\n",
    "#app.get_logging_widget()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "@app.event\n",
    "def render(ct, dt):\n",
    "    \n",
    "    app.window.clear()\n",
    "    \n",
    "    if state.shader == 0:\n",
    "        st0.set_channel(0, *get_shadertoy_audio(amp=5))   \n",
    "        st0.render(ct, dt)\n",
    "    else:\n",
    "        ba1.set_channel(0, *get_shadertoy_audio(amp=5))   \n",
    "        st1.render(ct, dt)\n",
    "        \n",
    "    keyboard_layout.draw()\n",
    "    \n",
    "    label0.draw()\n",
    "    label1.draw()\n",
    "    label2.draw()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "app.run()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.6"
  },
  "latex_envs": {
   "LaTeX_envs_menu_present": true,
   "autoclose": false,
   "autocomplete": true,
   "bibliofile": "biblio.bib",
   "cite_by": "apalike",
   "current_citInitial": 1,
   "eqLabelWithNumbers": true,
   "eqNumInitial": 1,
   "hotkeys": {
    "equation": "Ctrl-E",
    "itemize": "Ctrl-I"
   },
   "labels_anchors": false,
   "latex_user_defs": false,
   "report_style_numbering": false,
   "user_envs_cfg": false
  },
  "nbTranslate": {
   "displayLangs": [
    "en"
   ],
   "hotkey": "alt-t",
   "langInMainMenu": true,
   "sourceLang": "en",
   "targetLang": "cn",
   "useGoogleTranslate": true
  },
  "toc": {
   "base_numbering": 1,
   "nav_menu": {},
   "number_sections": true,
   "sideBar": true,
   "skip_h1_title": false,
   "title_cell": "Table of Contents",
   "title_sidebar": "Contents",
   "toc_cell": false,
   "toc_position": {},
   "toc_section_display": true,
   "toc_window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
